package no.weather.weatherProvider;

import java.security.InvalidParameterException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import no.weather.WeatherElement;
import no.weather.WeatherStation;
import no.weather.WeatherType;
import no.weather.misc.IProgress;
import no.weather.weatherProxy.wsKlima.WsKlimaProxy;
import android.content.Context;
import android.database.Cursor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;

public class WeatherProvider implements WeatherManagerInterface {
	private WeatherDatabase db;
	private final LocationManager locationManager;
	private WsKlimaProxy wsklima;

	public WeatherProvider(Context context) {
		db = new WeatherDatabase(context);
		locationManager = (LocationManager) context
				.getSystemService(Context.LOCATION_SERVICE);
		wsklima = new WsKlimaProxy(context);
	}

	public static Location getLocationFromCursor(Cursor cursor,
			Integer latColumn, Integer lonColumn, Location resiclerLocation) {
		if (resiclerLocation == null) {
			resiclerLocation = new Location("");
		}

		resiclerLocation.setLongitude(cursor.getDouble(lonColumn));
		resiclerLocation.setLatitude(cursor.getDouble(latColumn));
		return resiclerLocation;
	}

	public void addFavorite(String stationId, String stationProvider) {
		db.addFavorite(stationId, stationProvider);
	}

	public void deleteFavorite(String stationId, String stationProvider) {
		db.deleteFavorite(stationId, stationProvider);
	}

	public Cursor getAllStations() {
		return db.getAllStations(null);
	}

	public Cursor getAllStations(String orderBy) {
		return db.getAllStations(orderBy);
	}

	public Cursor getAllStations(String orderBy, CharSequence constraint) {
		return db.getAllStations(orderBy, constraint);
	}

	public WeatherDatabase getDB() {
		return db;
	}

	public Cursor getFavoriteStations(String orderBy) {
		return db.getFavoriteStations(orderBy);
	}

	private Location getLocation() {
		Criteria criteria = new Criteria();
		criteria.setAccuracy(Criteria.ACCURACY_FINE);
		return locationManager.getLastKnownLocation(locationManager
				.getBestProvider(criteria, true));
	}

	public WeatherStation getNearestStation() {
		return getNearestStation(null);
	}

	@SuppressWarnings("null")
	public WeatherStation getNearestStation(List<WeatherType> minTypes) {

		Location currentLocation = getLocation();
		Location tempLocation;
		List<Pair> distances = new LinkedList<Pair>();
		Cursor cursor = db.getAllStations(null);
		Integer latColumn = cursor
				.getColumnIndex(WeatherDatabase.STATION_LATITUDE);
		Integer lonColumn = cursor
				.getColumnIndex(WeatherDatabase.STATION_LONGTITUDE);
		if (cursor == null || cursor.getCount() == 0) {
			updateStations();
			return getNearestStation(minTypes);
		}
		cursor.moveToFirst();
		tempLocation = new Location("");
		tempLocation = getLocationFromCursor(cursor, latColumn, lonColumn,
				tempLocation);
		Pair pair = new Pair();
		cursor.moveToNext();
		while (!cursor.isLast()) {
			tempLocation = getLocationFromCursor(cursor, latColumn, lonColumn,
					tempLocation);
			pair = new Pair(cursor.getPosition(),
					tempLocation.distanceTo(currentLocation));
			// pair.setKey(cursor.getPosition());
			// pair.setValue(tempLocation.distanceTo(currentLocation));
			distances.add(pair);
			cursor.moveToNext();
		}

		Collections.sort(distances);
		WeatherStation station = null;
		if (minTypes != null) {
			for (int i = 0; i < distances.size(); i++) {
				cursor.moveToPosition(distances.get(i).getKey());
				String id = cursor.getString(cursor
						.getColumnIndex(WeatherDatabase.STATION_ID));
				String provider = cursor.getString(cursor
						.getColumnIndex(WeatherDatabase.STATION_PROVIDER));
				Boolean stationIsDeleted = false;
				try {
					station = getStation(id, provider, true);
				} catch (StationIsDeletedException e) {
					stationIsDeleted = true;
				}
				// If the station have all types return else go to next
				if (!stationIsDeleted && station.hasAllTypes(minTypes)) {
					break;
				}
				station = null;
			}

		} else {
			cursor.moveToPosition(distances.get(0).getKey());
			String id = cursor.getString(cursor
					.getColumnIndex(WeatherDatabase.STATION_ID));
			String provider = cursor.getString(cursor
					.getColumnIndex(WeatherDatabase.STATION_PROVIDER));
			try {
				station = getStation(id, provider, true);
			} catch (StationIsDeletedException e) {
				return getNearestStation(null);
			}
		}
		// Close cursor and database
		db.close();
		cursor.close();
		return station;
	}

	public WeatherStation getStation(String stationId, String stationProvider,
			boolean getProvidedElementIfUnknown)
			throws StationIsDeletedException {
		WeatherStation station = db.getStation(stationId, stationProvider);
		if (getProvidedElementIfUnknown && !station.getTypesIsKnown()) {
			getWeatherTypes(station);
		}

		return station;
	}

	public Cursor getStationsFromProvider(String provider, String columns[]) {
		return db.getStationsFromProvider(provider, columns);
	}

	public List<WeatherElement> getWeather(WeatherStation station, int year,
			int month, int day, int hour) throws StationIsDeletedException {
		if (!station.getTypesIsKnown()) {
			station.setTypes(getWeatherTypes(station.getId(),
					station.getProvider(), false));
		}

		if (station.getProvider().equals(WsKlimaProxy.PROVIDER))
			return wsklima.getWeather(station, year, month, day, hour);
		else
			throw new InvalidParameterException(station.getProvider()
					+ " is not provided in getWeatherNow");
	}

	public List<WeatherElement> getWeatherNow(WeatherStation station)
			throws StationIsDeletedException {
		return getWeatherNow(station,null,0);
	}

	public List<WeatherElement> getWeatherNow(WeatherStation station,IProgress progress, Integer maxAgeInSeconds)
			throws StationIsDeletedException {
		if (!station.getTypesIsKnown()) {
			station.setTypes(getWeatherTypes(station.getId(),
					station.getProvider(), false));
		}

		if (station.getProvider().equals(WsKlimaProxy.PROVIDER))
			return wsklima.getWeatherNow(station,progress,maxAgeInSeconds);
		else
			throw new InvalidParameterException(station.getProvider()
					+ " is not provided in getWeatherNow");
	}

	/**
	 * @param id
	 *            Station id
	 * @param stationProvider
	 *            Provider of the data
	 * @param includeNotProvidingElements
	 * @return
	 * @throws StationIsDeletedException
	 */
	public List<WeatherType> getWeatherTypes(String id, String stationProvider,
			Boolean includeNotProvidingElements)
			throws StationIsDeletedException {
		if (stationProvider.equals(WsKlimaProxy.PROVIDER)) {
			List<WeatherType> types = wsklima.getProvidedWeatherType(id,
					includeNotProvidingElements);
			db.setTypesToStation(types, id, stationProvider);
			return types;
		} else
			throw new InvalidParameterException("Unknown provider");
	}

	/**
	 * Takes in a station and either get the types from the database or get it
	 * from the proxy
	 * 
	 * @param station
	 *            To be filled in with types
	 * @throws StationIsDeletedException
	 *             if do not provides any types it is deleted
	 */
	public void getWeatherTypes(final WeatherStation station)
			throws StationIsDeletedException {
		station.setTypes(getWeatherTypes(station.getId(),
				station.getProvider(), false));
	}

	public void resetStationDb() {
		db.resetDb();
	}

	public void updateStations() {
		List<WeatherStation> stations = wsklima
				.getAllWeatherStationsFromTimeserie("2", false);
		for (WeatherStation weatherStation : stations)
			if (!db.isStationInDatabase(weatherStation.getId())) {
				try {
					db.add(weatherStation);
				} catch (StationIsDeletedException e) {
				}
			}
	}

	private class Pair implements Comparable<Pair> {
		private int Key;
		private float Value;

		public Pair() {
			super();
		}

		public Pair(int key, float value) {
			super();
			Key = key;
			Value = value;
		}

		@Override
		public Pair clone() {
			try {
				return (Pair) super.clone();
			} catch (CloneNotSupportedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return null;
		}

		@Override
		public int compareTo(Pair another) {
			// TODO Auto-generated method stub
			return (int) (Value - another.Value);
		}

		@Override
		public boolean equals(Object o) {
			if (o.getClass().equals(this.getClass()))
				return Value == ((Pair) o).Value;
			else
				return false;
		}

		public int getKey() {
			return Key;
		}

		@SuppressWarnings("unused")
		public float getValue() {
			return Value;
		}

		@SuppressWarnings("unused")
		public void setKey(int key) {
			Key = key;
		}

		@SuppressWarnings("unused")
		public void setValue(float value) {
			Value = value;
		}
	}

}
